﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace Muel.TypeScriptFacade.Core
{
    public class TypeScriptProperty
    {
        private PropertyInfo property;
        private TypeScriptServiceManager manager;

        public TypeScriptProperty(TypeScriptServiceManager manager, PropertyInfo property, bool isInherited)
        {
            this.manager = manager;
            this.property = property;

            // fix the name up if an alternative serialisation name is specified
            string propertyName = property.Name;
            var attribute = property.GetCustomAttributes<System.Runtime.Serialization.DataMemberAttribute>().FirstOrDefault();
            if (!string.IsNullOrEmpty(attribute.Name))
            {
                propertyName = attribute.Name;
            }

            this.Name = propertyName;
            this.IsInherited = isInherited;
        }
        
        private bool initialised = false;
        internal void Initialise()
        {
            if (!initialised)
            {
                memberType = manager.ObjectManager.Create(property.PropertyType);
            }
            initialised = true;
        }
        public bool IsInitialised { get { return initialised; } }

        private TypeScriptType memberType = null;
        public TypeScriptType MemberType
        {
            get
            {
                Initialise();
                return memberType;
            }
        }

        public string Name { get; private set; }
        public bool IsInherited { get; private set; }

        #region Output to TypeScript

        private const string valuePropertyConstructorFormat = "            this.{0} = (data.{1} == null) ? null : data.{2}; ";
        private const string valueArrayPropertyConstructorFormat = "            this.{0} = (data.{1} == null) ? null : data.{2}; ";
        private const string objectPropertyConstructorFormat = "            if (data.{0} != null) {{ this.{1} = {2}.Core.TypeRegister.Instantiate(data.{3}, {4}); }} ";
        private const string objectArrayPropertyConstructorFormat = "            if (data.{0} != null) {{ this.{1} = []; for (var i = 0; i < data.{2}.length; i++) {{ this.{3}.push({4}.Core.TypeRegister.Instantiate(data.{5}[i], {6})); }} }} ";

        /// <summary>
        /// {0}: Property name
        /// {1}: Property type
        /// </summary>
        private const string publicClassPropertyFormat = @"        public {0}: {1};";

        /// <summary>
        /// {0}: Property name
        /// {1}: Property type
        /// </summary>
        private const string protectedClassPropertyFormat = @"        protected _{0}: {1};";

        /// <summary>
        /// Creates the code to represent public properties and private fields on the TypeScript object
        /// </summary>
        /// <returns>TypeScript code</returns>
        internal string ToTypeScriptProperty()
        {
            string propertyType = MemberType.GetTypeScriptType();
            bool canSnapshot = MemberType.CanSnapshot;

            string publicProperty = string.Format(publicClassPropertyFormat, Name, propertyType);
            string privateProperty = string.Empty;
            if (canSnapshot)
            {
                privateProperty = Environment.NewLine + string.Format(protectedClassPropertyFormat, Name, propertyType);
            }
            return publicProperty + privateProperty;
        }
        
        /// <summary>
        /// Creates the code to put into the constructor
        /// </summary>
        /// <returns>TypeScript code</returns>
        internal string ToTypeScriptConstructor()
        {
            string propertyType = MemberType.GetTypeScriptType();
            bool canSnapshot = MemberType.CanSnapshot;
            bool arrayType = MemberType.IsArray;

            if (canSnapshot)
            {
                if (!arrayType)
                {
                    return string.Format(valuePropertyConstructorFormat, Name, Name, Name);
                }
                else
                {
                    return string.Format(valueArrayPropertyConstructorFormat, Name, Name, Name);
                }
            }
            else
            {
                if (!arrayType)
                {
                    return string.Format(objectPropertyConstructorFormat, Name, Name, this.manager.Settings.ServiceFacadeName, Name, propertyType);
                }
                else
                {
                    string underlyingPropertyType = MemberType.ElementType.GetTypeScriptType();
                    return string.Format(objectArrayPropertyConstructorFormat, Name, Name, Name, Name, this.manager.Settings.ServiceFacadeName, Name, underlyingPropertyType);
                }
            }
        }

        private const string valuePropertyHasChangesFormat = "            if (this._{0} != this.{1}) {{ return true; }} ";
        private const string valueArrayPropertyHasChangesFormat = "            if (!{0}.Utilities.Arr.Equals(this._{1}, this.{2})) {{ return true; }} ";
        private const string objectPropertyHasChangesFormat = "            if (this.{0} != null && this.{1}.{2}()) {{ return true; }} ";
        private const string objectArrayPropertyHasChangesFormat = "            if (this.{0} != null) {{ for (var i = 0; i < this.{1}.length; i++) {{ if (this.{2}[i].{3}()) {{ return true; }} }} }} ";

        /// <summary>
        /// Creates the code to put into the hasChanges function for this property
        /// </summary>
        /// <returns>TypeScript code</returns>
        internal string ToTypeScriptHasChanges()
        {
            string propertyType = MemberType.GetTypeScriptType();
            bool canSnapshot = MemberType.CanSnapshot;
            bool arrayType = MemberType.IsArray;
            string returnValue = string.Empty;
            if (canSnapshot)
            {
                if (!arrayType)
                {
                    returnValue = string.Format(valuePropertyHasChangesFormat, Name, Name);
                }
                else
                {
                    returnValue = string.Format(valueArrayPropertyHasChangesFormat, this.manager.Settings.ServiceFacadeName, Name, Name);
                }
            }
            else
            {
                if (!arrayType)
                {
                    returnValue = string.Format(objectPropertyHasChangesFormat, Name, Name, manager.Settings.ChangeTracking.HasChangesMethodName);
                }
                else
                {
                    returnValue = string.Format(objectArrayPropertyHasChangesFormat, Name, Name, Name, manager.Settings.ChangeTracking.HasChangesMethodName);
                }
            }
            return returnValue;
        }


        private const string valuePropertySnapshotFormat = "            this._{0} = this.{1}; ";
        private const string valueArrayPropertySnapshotFormat = "            if (this.{0} != null) {{ this._{1} = this.{2}.slice(0); }} ";
        private const string objectPropertySnapshotFormat = "            if (this.{0} != null) {{ this.{1}.{2}(); }} ";
        private const string objectArrayPropertySnapshotFormat = "            if (this.{0} != null) {{ for (var i = 0; i < this.{1}.length; i++) {{ this.{2}[i].{3}(); }} }} ";

        /// <summary>
        /// Creates the code to put into the snapshot function for this property
        /// </summary>
        /// <returns>TypeScript code</returns>
        internal string ToTypeScriptSnapshot()
        {
            string propertyType = MemberType.GetTypeScriptType();
            bool canSnapshot = MemberType.CanSnapshot;
            bool arrayType = MemberType.IsArray;
            string returnValue = string.Empty;
            if (canSnapshot)
            {
                if (!arrayType)
                {
                    returnValue = string.Format(valuePropertySnapshotFormat, Name, Name);
                }
                else
                {
                    returnValue = string.Format(valueArrayPropertySnapshotFormat, Name, Name, Name);
                }
            }
            else
            {
                if (!arrayType)
                {
                    returnValue = string.Format(objectPropertySnapshotFormat, Name, Name, manager.Settings.ChangeTracking.SnapshotMethodName);
                }
                else
                {
                    returnValue = string.Format(objectArrayPropertySnapshotFormat, Name, Name, Name, manager.Settings.ChangeTracking.SnapshotMethodName);
                }
            }
            return returnValue;
        }


        private const string valuePropertyRevertFormat = "            this.{0} = this._{1}; ";
        private const string valueArrayPropertyRevertFormat = "            if (this._{0} != null) {{ this.{1} = this._{2}.slice(0); }} ";
        private const string objectPropertyRevertFormat = "            if (this.{0} != null) {{ this.{1}.{2}(); }} ";
        private const string objectArrayPropertyRevertFormat = "            if (this.{0} != null) {{ for (var i = 0; i < this.{1}.length; i++) {{ this.{2}[i].{3}(); }} }} ";

        /// <summary>
        /// Creates the code to put into the revert function for this property
        /// </summary>
        /// <returns>TypeScript code</returns>
        internal string ToTypeScriptRevert()
        {
            string propertyType = MemberType.GetTypeScriptType();
            bool canSnapshot = MemberType.CanSnapshot;
            bool arrayType = MemberType.IsArray;
            string returnValue = string.Empty;
            if (canSnapshot)
            {
                if (!arrayType)
                {
                    returnValue = string.Format(valuePropertyRevertFormat, Name, Name);
                }
                else
                {
                    returnValue = string.Format(valueArrayPropertyRevertFormat, Name, Name, Name);
                }
            }
            else
            {
                if (!arrayType)
                {
                    returnValue = string.Format(objectPropertyRevertFormat, Name, Name, manager.Settings.ChangeTracking.RevertMethodName);
                }
                else
                {
                    returnValue = string.Format(objectArrayPropertyRevertFormat, Name, Name, Name, manager.Settings.ChangeTracking.RevertMethodName);
                }
            }
            return returnValue;
        }

        #endregion

    }
}
